function mxGraphView(graph)
{
	this.graph = graph;
	this.translate = new mxPoint();
	this.graphBounds = new mxRectangle();
	this.states = new mxDictionary();
};
mxGraphView.prototype = new mxEventSource();
mxGraphView.prototype.constructor = mxGraphView;
mxGraphView.prototype.EMPTY_POINT = new mxPoint();
mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
mxGraphView.prototype.captureDocumentGesture = true;
mxGraphView.prototype.rendering = true;
mxGraphView.prototype.graph = null;
mxGraphView.prototype.currentRoot = null;
mxGraphView.prototype.graphBounds = null;
mxGraphView.prototype.scale = 1;
mxGraphView.prototype.translate = null;
mxGraphView.prototype.updateStyle = false;
mxGraphView.prototype.getGraphBounds = function()
{
	return this.graphBounds;
};
mxGraphView.prototype.setGraphBounds = function(value)
{
	this.graphBounds = value;
};
mxGraphView.prototype.getBounds = function(cells)
{
	var result = null;

	if (cells != null && cells.length > 0)
	{
		var model = this.graph.getModel();

		for (var i = 0; i < cells.length; i++)
		{
			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
			{
				var state = this.getState(cells[i]);

				if (state != null)
				{
					if (result == null)
					{
						result = new mxRectangle(state.x, state.y, state.width, state.height);
					}
					else
					{
						result.add(state);
					}
				}
			}
		}
	}
	return result;
};
mxGraphView.prototype.setCurrentRoot = function(root)
{
	if (this.currentRoot != root)
	{
		var change = new mxCurrentRootChange(this, root);
		change.execute();
		var edit = new mxUndoableEdit(this, false);
		edit.add(change);
		this.fireEvent(mxEvent.UNDO, new mxEventObject([edit]));
		this.graph.sizeDidChange();
	}
	return root;
};
mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
{
	var oldScale = this.scale;
	var oldDx = this.translate.x;
	var oldDy = this.translate.y;

	if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
	{
		this.scale = scale;
		this.translate.x = dx;
		this.translate.y = dy;

		if (this.isEventsEnabled())
		{
			this.revalidate();
			this.graph.sizeDidChange();
		}
	}
	this.fireEvent(mxEvent.SCALE_AND_TRANSLATE, new mxEventObject([oldScale, scale, oldDx, oldDy, dx, dy]));
};
mxGraphView.prototype.getScale = function()
{
	return this.scale;
};
mxGraphView.prototype.setScale = function(scale)
{
	var oldScale = this.scale;

	if (this.scale != scale)
	{
		this.scale = scale;

		if (this.isEventsEnabled())
		{
			this.revalidate();
			this.graph.sizeDidChange();
		}
	}
	this.fireEvent(mxEvent.SCALE, new mxEventObject([oldScale, scale]));
};
mxGraphView.prototype.getTranslate = function()
{
	return this.translate;
};
mxGraphView.prototype.setTranslate = function(dx, dy)
{
	var oldDx = this.translate.x;
	var oldDy = this.translate.y;

	if (this.translate.x != dx || this.translate.y != dy)
	{
		this.translate.x = dx;
		this.translate.y = dy;

		if (this.isEventsEnabled())
		{
			this.revalidate();
			this.graph.sizeDidChange();
		}
	}
	this.fireEvent(mxEvent.TRANSLATE, new mxEventObject([oldDx, oldDy, dx, dy]));
};
mxGraphView.prototype.refresh = function()
{
	if (this.currentRoot != null)
	{
		this.clear();
	}
	this.revalidate();
};
mxGraphView.prototype.revalidate = function()
{
	this.invalidate();
	this.validate();
};
mxGraphView.prototype.clear = function(cell, force, recurse)
{
	var model = this.graph.getModel();
	cell = cell || model.getRoot();
	force = (force != null) ? force : false;
	recurse = (recurse != null) ? recurse : true;
	this.removeState(cell);

	if (recurse && (force || cell != this.currentRoot))
	{
		var childCount = model.getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			this.clear(model.getChildAt(cell, i), force);
		}
	}
	else
	{
		this.invalidate(cell);
	}
};
mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges, orderChanged)
{
	var model = this.graph.getModel();
	cell = cell || model.getRoot();
	recurse = (recurse != null) ? recurse : true;
	includeEdges = (includeEdges != null) ? includeEdges : true;
	orderChanged = (orderChanged != null) ? orderChanged : false;
	var state = this.getState(cell);

	if (state != null)
	{
		state.invalid = true;

		if (orderChanged)
		{
			state.orderChanged = true;
		}
	}

	if (recurse)
	{
		var childCount = model.getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			var child = model.getChildAt(cell, i);
			this.invalidate(child, recurse, includeEdges);
		}
	}

	if (includeEdges)
	{
		var edgeCount = model.getEdgeCount(cell);

		for (var i = 0; i < edgeCount; i++)
		{
			this.invalidate(model.getEdgeAt(cell, i), recurse, false);
		}
	}
};
mxGraphView.prototype.validate = function(cell)
{
	var t0 = mxLog.enter('mxGraphView.validate');
	window.status = mxResources.get(this.updatingDocumentResource) || this.updatingDocumentResource;
	cell = cell || ((this.currentRoot != null) ? this.currentRoot : this.graph.getModel().getRoot());
	this.validateBounds(null, cell);
	this.setGraphBounds(this.validatePoints(null, cell));
	this.validateBackground();
	window.status = mxResources.get(this.doneResource) || this.doneResource;
	mxLog.leave('mxGraphView.validate', t0);
};
mxGraphView.prototype.validateBackground = function()
{
	var bg = this.graph.getBackgroundImage();

	if (bg != null)
	{
		if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
		{
			if (this.backgroundImage != null)
			{
				this.backgroundImage.destroy();
			}
			var bounds = new mxRectangle(0, 0, 1, 1);
			this.backgroundImage = new mxImageShape(bounds, bg.src);
			this.backgroundImage.dialect = this.graph.dialect;
			this.backgroundImage.init(this.backgroundPane);
		}
		this.redrawBackgroundImage(this.backgroundImage, bg);
	}
	else if (this.backgroundImage != null)
	{
		this.backgroundImage.destroy();
		this.backgroundImage = null;
	}

	if (this.graph.pageVisible)
	{
		var fmt = this.graph.pageFormat;
		var ps = this.scale * this.graph.pageScale;
		var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y, fmt.width * ps,
			fmt.height * ps);

		if (this.backgroundPageShape == null)
		{
			this.backgroundPageShape = new mxRectangleShape(bounds, 'white', 'black');
			this.backgroundPageShape.scale = this.scale;
			this.backgroundPageShape.isShadow = true;
			this.backgroundPageShape.dialect = this.graph.dialect;
			this.backgroundPageShape.init(this.backgroundPane);
			var self = this;
			mxEvent.addListener(this.backgroundPageShape.node, 'dblclick', function(evt)
			{
				self.graph.dblClick(evt);
				mxEvent.consume(evt);
			});

			mxEvent.addListener(this.backgroundPageShape.node, 'mousedown', function(evt)
			{
				self.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
			});
			mxEvent.addListener(this.backgroundPageShape.node, 'mousemove', function(evt)
			{
				if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
				{
					graph.tooltipHandler.hide();
				}

				if (graph.isMouseDown && !mxEvent.isConsumed(evt))
				{
					self.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
				}
			});
			mxEvent.addListener(this.backgroundPageShape.node, 'mouseup', function(evt)
			{
				self.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
			});
		}
		else
		{
			this.backgroundPageShape.scale = this.scale;
			this.backgroundPageShape.bounds = bounds;
			this.backgroundPageShape.redraw();
		}
	}
	else if (this.backgroundPageShape != null)
	{
		this.backgroundPageShape.destroy();
		this.backgroundPageShape = null;
	}
};
mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
{
	backgroundImage.scale = this.scale;
	backgroundImage.bounds.x = this.scale * this.translate.x;
	backgroundImage.bounds.y = this.scale * this.translate.y;
	backgroundImage.bounds.width = this.scale * bg.width;
	backgroundImage.bounds.height = this.scale * bg.height;
	backgroundImage.redraw();
};
mxGraphView.prototype.validateBounds = function(parentState, cell)
{
	var model = this.graph.getModel();
	var state = this.getState(cell, true);

	if (state != null && state.invalid)
	{
		if (!this.graph.isCellVisible(cell))
		{
			this.removeState(cell);
		}
		else if (cell != this.currentRoot && parentState != null)
		{
			state.absoluteOffset.x = 0;
			state.absoluteOffset.y = 0;
			state.origin.x = parentState.origin.x;
			state.origin.y = parentState.origin.y;
			var geo = this.graph.getCellGeometry(cell);

			if (geo != null)
			{
				if (!model.isEdge(cell))
				{
					var offset = geo.offset || this.EMPTY_POINT;

					if (geo.relative)
					{
						state.origin.x += geo.x * parentState.width / this.scale + offset.x;
						state.origin.y += geo.y * parentState.height / this.scale + offset.y;
					}
					else
					{
						state.absoluteOffset.x = this.scale * offset.x;
						state.absoluteOffset.y = this.scale * offset.y;
						state.origin.x += geo.x;
						state.origin.y += geo.y;
					}
				}
				state.x = this.scale * (this.translate.x + state.origin.x);
				state.y = this.scale * (this.translate.y + state.origin.y);
				state.width = this.scale * geo.width;
				state.height = this.scale * geo.height;

				if (model.isVertex(cell))
				{
					this.updateVertexLabelOffset(state);
				}
			}
		}
		var offset = this.graph.getChildOffsetForCell(cell);

		if (offset != null)
		{
			state.origin.x += offset.x;
			state.origin.y += offset.y;
		}
	}

	if (state != null && (!this.graph.isCellCollapsed(cell) || cell == this.currentRoot))
	{
		var childCount = model.getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			var child = model.getChildAt(cell, i);
			this.validateBounds(state, child);
		}
	}
};
mxGraphView.prototype.updateVertexLabelOffset = function(state)
{
	var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);

	if (horizontal == mxConstants.ALIGN_LEFT)
	{
		state.absoluteOffset.x -= state.width;
	}
	else if (horizontal == mxConstants.ALIGN_RIGHT)
	{
		state.absoluteOffset.x += state.width;
	}
	var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

	if (vertical == mxConstants.ALIGN_TOP)
	{
		state.absoluteOffset.y -= state.height;
	}
	else if (vertical == mxConstants.ALIGN_BOTTOM)
	{
		state.absoluteOffset.y += state.height;
	}
};
mxGraphView.prototype.validatePoints = function(parentState, cell)
{
	var minX = null;
	var minY = null;
	var maxX = 0;
	var maxY = 0;
	var model = this.graph.getModel();
	var state = this.getState(cell);

	if (state != null)
	{
		if (state.invalid)
		{
			var geo = this.graph.getCellGeometry(cell);

			if (model.isEdge(cell))
			{
				var source = this.getVisibleTerminal(cell, true);

				if (source != null && !model.isAncestor(source, cell))
				{
					var p = model.getParent(source);
					var pstate = this.getState(p);
					this.validatePoints(pstate, source);
				}
				var target = this.getVisibleTerminal(cell, false);

				if (target != null && !model.isAncestor(target, cell))
				{
					var p = model.getParent(target);
					var pstate = this.getState(p);
					this.validatePoints(pstate, target);
				}
				this.setTerminalPoints(state);
				this.updatePoints(state, geo.points, source, target);
				this.updateTerminalPoints(state, source, target);
				this.updateEdgeBounds(state);
				this.updateEdgeLabelOffset(state);
			}
			else if (geo != null && geo.relative && parentState != null && model.isEdge(parentState.cell))
			{
				var origin = this.getPoint(parentState, geo);

				if (origin != null)
				{
					state.x = origin.x;
					state.y = origin.y;
					origin.x = (origin.x / this.scale) - this.translate.x;
					origin.y = (origin.y / this.scale) - this.translate.y;
					state.origin = origin;
					this.childMoved(parentState, state);
				}
			}
			state.invalid = false;

			if (this.isRendering() && cell != this.currentRoot)
			{
				this.graph.cellRenderer.redraw(state);
			}
		}

		if (model.isEdge(cell) || model.isVertex(cell))
		{
			var box = (state.text != null && (!this.graph.isHtmlLabel(cell) || !this.graph.isLabelClipped(cell)))
				? state.text.boundingBox : null;

			if (box != null)
			{
				minX = Math.min(state.x, box.x);
				minY = Math.min(state.y, box.y);
				maxX = Math.max(state.x + state.width, box.x + box.width);
				maxY = Math.max(state.y + state.height, box.y + box.height);
			}
			else
			{
				minX = state.x;
				minY = state.y;
				maxX = state.x + state.width;
				maxY = state.y + state.height;
			}
		}
	}

	if (state != null && (!this.graph.isCellCollapsed(cell) || cell == this.currentRoot))
	{
		var childCount = model.getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			var child = model.getChildAt(cell, i);
			var bounds = this.validatePoints(state, child);
			minX = (minX != null) ? Math.min(minX, bounds.x) : bounds.x;
			minY = (minY != null) ? Math.min(minY, bounds.y) : bounds.y;
			maxX = Math.max(maxX, bounds.x + bounds.width);
			maxY = Math.max(maxY, bounds.y + bounds.height);
		}
	}
	return new mxRectangle(minX, minY, maxX - minX, maxY - minY);
};
mxGraphView.prototype.childMoved = function(parentState, childState)
{
	var cell = childState.cell;

	if (!this.graph.isCellCollapsed(cell) || cell == this.currentRoot)
	{
		var model = this.graph.getModel();
		var childCount = model.getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			this.validateBounds(childState, model.getChildAt(cell, i));
		}
	}
};
mxGraphView.prototype.setTerminalPoints = function(state)
{
	var tr = this.translate;
	var s = this.scale;
	var edge = state.cell;
	var orig = state.origin;
	var geo = this.graph.getCellGeometry(edge);
	var pt = geo.getTerminalPoint(true);

	if (pt != null)
	{
		pt = new mxPoint(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
		state.setAbsoluteTerminalPoint(pt, true);
	}
	else
	{
		state.setAbsoluteTerminalPoint(null, true);
	}
	pt = geo.getTerminalPoint(false);

	if (pt != null)
	{
		pt = new mxPoint(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
		state.setAbsoluteTerminalPoint(pt, false);
	}
	else
	{
		state.setAbsoluteTerminalPoint(null, false);
	}
};
mxGraphView.prototype.updatePoints = function(state, points, source, target)
{
	if (state != null)
	{
		var pts = [];
		pts.push(state.absolutePoints[0]);
		var edgeStyle = this.getEdgeStyle(state, points, source, target);

		if (edgeStyle != null)
		{
			var src = this.getState(source);
			var trg = this.getState(target);
			edgeStyle(state, src, trg, points, pts);
		}
		else if (points != null)
		{
			for (var i = 0; i < points.length; i++)
			{
				if (points[i] != null)
				{
					var pt = mxUtils.clone(points[i]);
					pts.push(this.transformControlPoint(state, pt));
				}
			}
		}
		var tmp = state.absolutePoints;
		pts.push(tmp[tmp.length - 1]);
		state.absolutePoints = pts;
	}
};
mxGraphView.prototype.transformControlPoint = function(state, pt)
{
	var origin = state.origin;
	return new mxPoint((pt.x + this.translate.x + origin.x) * this.scale, (pt.y + this.translate.y + origin.y)
		* this.scale);
}
mxGraphView.prototype.getEdgeStyle = function(edgeState, points, source, target)
{
	var edgeStyle = (source != null && source == target)
		? mxUtils.getValue(edgeState.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle)
		: (!mxUtils.getValue(edgeState.style, mxConstants.STYLE_NOEDGESTYLE, false)
			? edgeState.style[mxConstants.STYLE_EDGE] : null);

	if (typeof (edgeStyle) == "string")
	{
		var tmp = mxStyleRegistry.getValue(edgeStyle);

		if (tmp == null && edgeStyle.indexOf('.') > 0)
		{
			tmp = mxUtils.eval(edgeStyle);
		}
		edgeStyle = tmp;
	}

	if (typeof (edgeStyle) == "function")
	{
		return edgeStyle;
	}
	return null;
};
mxGraphView.prototype.updateTerminalPoints = function(state, source, target)
{
	if (target != null)
	{
		this.updateTerminalPoint(state, target, source, false);
	}

	if (source != null)
	{
		this.updateTerminalPoint(state, source, target, true);
	}
};
mxGraphView.prototype.updateTerminalPoint = function(state, start, end, isSource)
{
	var pt = this.getPerimeterPoint(state, start, end, isSource);
	state.setAbsoluteTerminalPoint(pt, isSource);
};
mxGraphView.prototype.getPerimeterPoint = function(state, start, end, isSource)
{
	var point = null;
	var term = this.getState(start);

	if (term != null)
	{
		var perimeter = this.getPerimeterFunction(term);
		var next = this.getNextPoint(state, end, isSource);

		if (perimeter != null && next != null)
		{
			var bounds = this.getPerimeterBounds(term, state, isSource);

			if (bounds.width > 0 || bounds.height > 0)
			{
				point = perimeter(bounds, state, term, isSource, next);
			}
		}

		if (point == null)
		{
			point = this.getPoint(term);
		}
	}
	return point;
};
mxGraphView.prototype.getRoutingCenterX = function(state)
{
	var f = (state.style != null) ? parseFloat(state.style[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
	return state.getCenterX() + f * state.width;
};
mxGraphView.prototype.getRoutingCenterY = function(state)
{
	var f = (state.style != null) ? parseFloat(state.style[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
	return state.getCenterY() + f * state.height;
};
mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
{
	var border = 0;

	if (edge != null)
	{
		border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0)
		border += parseFloat(edge.style[(isSource)
			? mxConstants.STYLE_SOURCE_PERIMETER_SPACING : mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
	}

	if (terminal != null)
	{
		border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
	}
	return terminal.getPerimeterBounds(border * this.scale);
};
mxGraphView.prototype.getPerimeterFunction = function(state)
{
	var perimeter = state.style[mxConstants.STYLE_PERIMETER];

	if (typeof (perimeter) == "string")
	{
		var tmp = mxStyleRegistry.getValue(perimeter);

		if (tmp == null && perimeter.indexOf('.') > 0)
		{
			tmp = mxUtils.eval(perimeter);
		}
		perimeter = tmp;
	}

	if (typeof (perimeter) == "function")
	{
		return perimeter;
	}
	return null;
};
mxGraphView.prototype.getNextPoint = function(state, opposite, isSource)
{
	var point = null;
	var pts = state.absolutePoints;

	if (pts != null && (isSource || pts.length > 2 || opposite == null))
	{
		var count = pts.length;
		point = pts[(isSource) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
	}

	if (point == null && opposite != null)
	{
		var oppositeState = this.getState(opposite);

		if (oppositeState != null)
		{
			point = new mxPoint(oppositeState.getCenterX(), oppositeState.getCenterY());
		}
	}
	return point;
};
mxGraphView.prototype.getVisibleTerminal = function(edge, isSource)
{
	var model = this.graph.getModel();
	var result = model.getTerminal(edge, isSource);
	var best = result;

	while (result != null && result != this.currentRoot)
	{
		if (!this.graph.isCellVisible(best) || this.graph.isCellCollapsed(result))
		{
			best = result;
		}
		result = model.getParent(result);
	}
	if(model.getParent(best) == model.getRoot())
	{
		best = null;
	}
	return best;
};
mxGraphView.prototype.updateEdgeBounds = function(state)
{
	var points = state.absolutePoints;
	state.length = 0;

	if (points != null && points.length > 0)
	{
		var p0 = points[0];
		var pe = points[points.length - 1];

		if (p0 == null || pe == null)
		{

			this.clear(state.cell, true);
		}
		else
		{
			if (p0.x != pe.x || p0.y != pe.y)
			{
				var dx = pe.x - p0.x;
				var dy = pe.y - p0.y;
				state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
			}
			else
			{
				state.terminalDistance = 0;
			}
			var length = 0;
			var segments = [];
			var pt = p0;

			if (pt != null)
			{
				var minX = pt.x;
				var minY = pt.y;
				var maxX = minX;
				var maxY = minY;

				for (var i = 1; i < points.length; i++)
				{
					var tmp = points[i];

					if (tmp != null)
					{
						var dx = pt.x - tmp.x;
						var dy = pt.y - tmp.y;
						var segment = Math.sqrt(dx * dx + dy * dy);
						segments.push(segment);
						length += segment;
						pt = tmp;
						minX = Math.min(pt.x, minX);
						minY = Math.min(pt.y, minY);
						maxX = Math.max(pt.x, maxX);
						maxY = Math.max(pt.y, maxY);
					}
				}
				state.length = length;
				state.segments = segments;
				var markerSize = 1;
				state.x = minX;
				state.y = minY;
				state.width = Math.max(markerSize, maxX - minX);
				state.height = Math.max(markerSize, maxY - minY);
			}
		}
	}
};
mxGraphView.prototype.getPoint = function(state, geometry)
{
	var x = state.getCenterX();
	var y = state.getCenterY();

	if (state.segments != null && (geometry == null || geometry.relative))
	{
		var gx = (geometry != null) ? geometry.x / 2 : 0;
		var pointCount = state.absolutePoints.length;
		var dist = (gx + 0.5) * state.length;
		var segment = state.segments[0];
		var length = 0;
		var index = 1;

		while (dist > length + segment && index < pointCount - 1)
		{
			length += segment;
			segment = state.segments[index++];
		}

		if (segment != 0)
		{
			var factor = (dist - length) / segment;
			var p0 = state.absolutePoints[index - 1];
			var pe = state.absolutePoints[index];

			if (p0 != null && pe != null)
			{
				var gy = 0;
				var offsetX = 0;
				var offsetY = 0;

				if (geometry != null)
				{
					gy = geometry.y;
					var offset = geometry.offset;

					if (offset != null)
					{
						offsetX = offset.x;
						offsetY = offset.y;
					}
				}
				var dx = pe.x - p0.x;
				var dy = pe.y - p0.y;
				var nx = dy / segment;
				var ny = dx / segment;
				x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
				y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
			}
		}
	}
	else if (geometry != null)
	{
		var offset = geometry.offset;

		if (offset != null)
		{
			x += offset.x;
			y += offset.y;
		}
	}
	return new mxPoint(x, y);
};
mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
{
	var model = this.graph.getModel();
	var geometry = model.getGeometry(edgeState.cell);

	if (geometry != null)
	{
		var pointCount = edgeState.absolutePoints.length;

		if (geometry.relative && pointCount > 1)
		{
			var totalLength = edgeState.length;
			var segments = edgeState.segments;
			var p0 = edgeState.absolutePoints[0];
			var pe = edgeState.absolutePoints[1];
			var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
			var index = 0;
			var tmp = 0;
			var length = 0;

			for (var i = 2; i < pointCount; i++)
			{
				tmp += segments[i - 2];
				pe = edgeState.absolutePoints[i];
				var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);

				if (dist <= minDist)
				{
					minDist = dist;
					index = i - 1;
					length = tmp;
				}
				p0 = pe;
			}
			var seg = segments[index];
			p0 = edgeState.absolutePoints[index];
			pe = edgeState.absolutePoints[index + 1];
			var x2 = p0.x;
			var y2 = p0.y;
			var x1 = pe.x;
			var y1 = pe.y;
			var px = x;
			var py = y;
			var xSegment = x2 - x1;
			var ySegment = y2 - y1;
			px -= x1;
			py -= y1;
			var projlenSq = 0;
			px = xSegment - px;
			py = ySegment - py;
			var dotprod = px * xSegment + py * ySegment;

			if (dotprod <= 0.0)
			{
				projlenSq = 0;
			}
			else
			{
				projlenSq = dotprod * dotprod / (xSegment * xSegment + ySegment * ySegment);
			}
			var projlen = Math.sqrt(projlenSq);

			if (projlen > seg)
			{
				projlen = seg;
			}
			var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y));
			var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);

			if (direction == -1)
			{
				yDistance = -yDistance;
			}
			return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2, yDistance / this.scale);
		}
	}
	return new mxPoint();
};
mxGraphView.prototype.updateEdgeLabelOffset = function(state)
{
	var points = state.absolutePoints;
	state.absoluteOffset.x = state.getCenterX();
	state.absoluteOffset.y = state.getCenterY();

	if (points != null && points.length > 0 && state.segments != null)
	{
		var geometry = this.graph.getCellGeometry(state.cell);

		if (geometry.relative)
		{
			var offset = this.getPoint(state, geometry);

			if (offset != null)
			{
				state.absoluteOffset = offset;
			}
		}
		else
		{
			var p0 = points[0];
			var pe = points[points.length - 1];

			if (p0 != null && pe != null)
			{
				var dx = pe.x - p0.x;
				var dy = pe.y - p0.y;
				var x0 = 0;
				var y0 = 0;
				var off = geometry.offset;

				if (off != null)
				{
					x0 = off.x;
					y0 = off.y;
				}
				var x = p0.x + dx / 2 + x0 * this.scale;
				var y = p0.y + dy / 2 + y0 * this.scale;
				state.absoluteOffset.x = x;
				state.absoluteOffset.y = y;
			}
		}
	}
};
mxGraphView.prototype.getState = function(cell, create)
{
	create = create || false;
	var state = null;

	if (cell != null)
	{
		state = this.states.get(cell);

		if (this.graph.isCellVisible(cell))
		{
			if (state == null && create && this.graph.isCellVisible(cell))
			{
				state = this.createState(cell);
				this.states.put(cell, state);
			}
			else if (create && state != null && this.updateStyle)
			{
				state.style = this.graph.getCellStyle(cell);
			}
		}
	}
	return state;
};
mxGraphView.prototype.isRendering = function()
{
	return this.rendering;
};
mxGraphView.prototype.setRendering = function(value)
{
	this.rendering = value;
};
mxGraphView.prototype.getStates = function()
{
	return this.states;
};
mxGraphView.prototype.setStates = function(value)
{
	this.states = value;
};
mxGraphView.prototype.getCellStates = function(cells)
{
	if (cells == null)
	{
		return this.states;
	}
	else
	{
		var result = [];

		for (var i = 0; i < cells.length; i++)
		{
			var state = this.getState(cells[i]);

			if (state != null)
			{
				result.push(state);
			}
		}
		return result;
	}
};
mxGraphView.prototype.removeState = function(cell)
{
	var state = null;

	if (cell != null)
	{
		state = this.states.remove(cell);

		if (state != null)
		{
			this.graph.cellRenderer.destroy(state);
			state.destroy();
		}
	}
	return state;
};
mxGraphView.prototype.createState = function(cell)
{
	var style = this.graph.getCellStyle(cell);
	var state = new mxCellState(this, cell, style);

	if (this.isRendering())
	{
		this.graph.cellRenderer.initialize(state);
	}
	return state;
};
mxGraphView.prototype.getCanvas = function()
{
	return this.canvas;
};
mxGraphView.prototype.getBackgroundPane = function()
{
	return this.backgroundPane;
};
mxGraphView.prototype.getDrawPane = function()
{
	return this.drawPane;
};
mxGraphView.prototype.getOverlayPane = function()
{
	return this.overlayPane;
};
mxGraphView.prototype.isContainerEvent = function(evt)
{
	var source = mxEvent.getSource(evt);
	var bgNode = (this.backgroundImage != null) ? this.backgroundImage.node : null;
	return (source == this.graph.container || source.parentNode == bgNode || source == this.canvas.parentNode
		|| source == this.canvas || source == this.backgroundPane || source == this.drawPane
		|| source == this.overlayPane);
};
mxGraphView.prototype.isScrollEvent = function(evt)
{
	var offset = mxUtils.getOffset(this.graph.container);
	var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
	var outWidth = this.graph.container.offsetWidth;
	var inWidth = this.graph.container.clientWidth;

	if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
	{
		return true;
	}
	var outHeight = this.graph.container.offsetHeight;
	var inHeight = this.graph.container.clientHeight;

	if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
	{
		return true;
	}
	return false;
};
mxGraphView.prototype.init = function()
{
	var graph = this.graph;
	var container = graph.container;

	if (container != null)
	{
		var self = this;
		mxEvent.addListener(container, 'mousedown', function(evt)
		{
			if (self.isContainerEvent(evt)
				&& ((!mxClient.IS_IE && !mxClient.IS_GC && !mxClient.IS_OP && !mxClient.IS_SF)
					|| !self.isScrollEvent(evt)))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
			}
		});
		mxEvent.addListener(container, 'mousemove', function(evt)
		{
			if (self.isContainerEvent(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
			}
		});
		mxEvent.addListener(container, 'mouseup', function(evt)
		{
			if (self.isContainerEvent(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
			}
		});
		mxEvent.addListener(container, 'dblclick', function(evt)
		{
			graph.dblClick(evt);
			mxEvent.consume(evt);
		});

		mxEvent.addListener(document, 'mousedown', function(evt)
		{
			if (self.isContainerEvent(evt))
			{
				graph.panningHandler.hideMenu();
			}
		});
		mxEvent.addListener(document, 'mousemove', function(evt)
		{
			if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
			{
				graph.tooltipHandler.hide();
			}

			if (self.captureDocumentGesture && graph.isMouseDown && !mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
			}
		});
		mxEvent.addListener(document, 'mouseup', function(evt)
		{
			if (self.captureDocumentGesture)
			{
				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
			}
		});
	}

	if (graph.dialect == mxConstants.DIALECT_SVG)
	{
		this.createSvg();
	}
	else if (graph.dialect == mxConstants.DIALECT_VML)
	{
		this.createVml();
	}
	else
	{
		this.createHtml();
	}
};
mxGraphView.prototype.createHtml = function()
{
	var container = this.graph.container;

	if (container != null)
	{
		this.canvas = this.createHtmlPane();

		this.backgroundPane = this.createHtmlPane(1, 1);
		this.drawPane = this.createHtmlPane(1, 1);
		this.overlayPane = this.createHtmlPane(1, 1);
		this.canvas.appendChild(this.backgroundPane);
		this.canvas.appendChild(this.drawPane);
		this.canvas.appendChild(this.overlayPane);
		container.appendChild(this.canvas);
	}
};
mxGraphView.prototype.createHtmlPane = function(width, height)
{
	var pane = document.createElement('DIV');

	if (width != null && height != null)
	{
		pane.style.position = 'absolute';
		pane.style.left = '0px';
		pane.style.top = '0px';
		pane.style.width = width + 'px';
		pane.style.height = height + 'px';
	}
	else
	{
		pane.style.position = 'relative';
	}
	return pane;
};
mxGraphView.prototype.createVml = function()
{
	var container = this.graph.container;

	if (container != null)
	{
		var width = container.offsetWidth;
		var height = container.offsetHeight;
		this.canvas = this.createVmlPane(width, height);
		this.backgroundPane = this.createVmlPane(width, height);
		this.drawPane = this.createVmlPane(width, height);
		this.overlayPane = this.createVmlPane(width, height);
		this.canvas.appendChild(this.backgroundPane);
		this.canvas.appendChild(this.drawPane);
		this.canvas.appendChild(this.overlayPane);
		container.appendChild(this.canvas);
	}
};
mxGraphView.prototype.createVmlPane = function(width, height)
{
	var pane = document.createElement('v:group');

	pane.style.position = 'absolute';
	pane.style.left = '0px';
	pane.style.top = '0px';
	pane.style.width = width + 'px';
	pane.style.height = height + 'px';
	pane.setAttribute('coordsize', width + ',' + height);
	pane.setAttribute('coordorigin', '0,0');
	return pane;
};
mxGraphView.prototype.createSvg = function()
{
	var container = this.graph.container;
	this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
	this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
	this.canvas.appendChild(this.backgroundPane);
	this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
	this.canvas.appendChild(this.drawPane);
	this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
	this.canvas.appendChild(this.overlayPane);
	var root = document.createElementNS(mxConstants.NS_SVG, 'svg');

	var self = this;
	var onResize = function(evt)
	{
		if (self.graph.container != null)
		{
			var width = self.graph.container.offsetWidth;
			var height = self.graph.container.offsetHeight;
			var bounds = self.getGraphBounds();
			root.setAttribute('width', Math.max(width, bounds.width));
			root.setAttribute('height', Math.max(height, bounds.height));
		}
	};
	mxEvent.addListener(window, 'resize', onResize);

	if (mxClient.IS_OP)
	{
		onResize();
	}
	root.appendChild(this.canvas);

	if (container != null)
	{
		container.appendChild(root);
		var style = mxUtils.getCurrentStyle(container);

		if (style.position == 'static')
		{
			container.style.position = 'relative';
		}
	}
};
mxGraphView.prototype.destroy = function()
{
	var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;

	if (root == null)
	{
		root = this.canvas;
	}

	if (root != null && root.parentNode != null)
	{
		this.clear(this.currentRoot, true);
		mxEvent.removeAllListeners(document);
		mxEvent.release(this.graph.container);
		root.parentNode.removeChild(root);
		this.canvas = null;
		this.backgroundPane = null;
		this.drawPane = null;
		this.overlayPane = null;
	}
};
function mxCurrentRootChange(view, root)
{
	this.view = view;
	this.root = root;
	this.previous = root;
	this.isUp = root == null;

	if (!this.isUp)
	{
		var tmp = this.view.currentRoot;
		var model = this.view.graph.getModel();

		while (tmp != null)
		{
			if (tmp == root)
			{
				this.isUp = true;
				break;
			}
			tmp = model.getParent(tmp);
		}
	}
};
mxCurrentRootChange.prototype.execute = function()
{
	var tmp = this.view.currentRoot;
	this.view.currentRoot = this.previous;
	this.previous = tmp;
	var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);

	if (translate != null)
	{
		this.view.translate = new mxPoint(-translate.x, -translate.y);
	}
	var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
	this.view.fireEvent(name, new mxEventObject([this.previous, this.view.currentRoot]));

	if (this.isUp)
	{
		this.view.clear(this.view.currentRoot, true);
		this.view.validate();
	}
	else
	{
		this.view.refresh();
	}
	this.isUp = !this.isUp;
};